package com.gitee.l0km.codegen.webclient;

import static com.gitee.l0km.com4j.basex.bean.jdk.BeanPropertySupportImpl.BEAN_SUPPORT;

import java.beans.PropertyDescriptor;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.gitee.l0km.codegen.base.CodeGenUtils;
import com.gitee.l0km.com4j.base.Assert;
import com.gitee.l0km.com4j.base.BinaryUtils;
import com.gitee.l0km.com4j.base.Configuration;
import com.gitee.l0km.com4j.base.Judge;

/**
 * 用于从soapcpp2生成的gsoap stub头文件中解析读取生成C++代码所需要的数据
 * @author guyadong
 *
 */
public class GSoapHeaderHelper  implements GSoapConstants,WebClientConstants{
	private static final Logger logger = LoggerFactory.getLogger(GSoapClient.class);
	/**
	 * gsoap properties
	 */
	private static final Configuration prop=GSoapProperties.getInstance();
	/**
	 * gsoap代码中下划线符号的替换字符串
	 */
	private static final String underscore;
	static {
		underscore=prop.getProperty(PROP_UNDERSCORE).trim();
		Assert.notEmpty(underscore, "underscore");
	}
	/**
	 * gsoap stub头文件文本内容(xxxxStub.h)
	 */
	private final String stubHeader;
	/**
	 * namespace->prefix映射
	 */
	private final Map<String, String> gsoapNamespaceMap;
	/**
	 * 所有wsdl名称-->stub类型映射
	 */
	private final Map<String, String> typeNameMap;
	/**
	 * 所有引用类型的java Class-->stub类型映射
	 */
	private final Map<Class<?>, String> referenceClassMap;
	
	/**
	 * 引用类型的属性映射
	 */
	private final Map<Class<?>,Map<String, PropertyDescriptor>> refClassPropertiesMap;
	/**
	 * stub类型的属性映射
	 */
	private Map<String, Map<String, String>> stubClassProppertiesMap;
	/**
	 * gsoap stub源代码文件前缀
	 */
	private final String stubPrefix;

	/**
	 * gsoapstub源代码文件夹位置
	 */
	private final File stubFolder;
	private final Class<?> serviceClass;

	/**
	 * @param stubFolder gsoap stub代码文件夹
	 * @param serviceClass TODO
	 * @param stubPrefix gsoap stub代码文件前缀
	 */
	public GSoapHeaderHelper(File stubFolder, Class<?> serviceClass, String stubPrefix) {
		Assert.notNull(stubFolder, "stubFolder");
		Assert.notNull(serviceClass, "serviceClass");
		this.serviceClass=serviceClass;
		this.stubFolder= stubFolder;
		this.stubPrefix= Judge.isEmpty(stubPrefix) ?this.serviceClass.getSimpleName().toLowerCase():stubPrefix;		
		this.stubHeader=load(new File(this.stubFolder,this.stubPrefix+"Stub.h"));
		this.gsoapNamespaceMap=createGSoapNamespaceMap(stubHeader);
		this.typeNameMap=createTypeNameMap(stubHeader);
		this.referenceClassMap=createReferenceClassMap(stubHeader);
		this.refClassPropertiesMap=createBeanClassPropertiesMap(referenceClassMap.keySet());
		this.stubClassProppertiesMap=createStubClassPropDefMap(CodeGenUtils.toSet(referenceClassMap.values()));
	}
	/**
	 * @return referenceClassMap
	 */
	public Map<Class<?>, String> getReferenceClassMap() {
		return referenceClassMap;
	}
	
	/**
	 * @return 所有Bean类型映射
	 */
	public Map<Class<?>, String> getBeanClassMap() {
		Map<Class<?>, String> beanMap=new HashMap<Class<?>, String>();
		for(Entry<Class<?>, String> entry:referenceClassMap.entrySet()){
			if(!Exception.class.isAssignableFrom(entry.getKey())){
				beanMap.put(entry.getKey(), entry.getValue());
			}
		}
		return beanMap;
	}
	/**
	 * @param method
	 * @param prefix
	 * @param suffix
	 * @see #getStubPortName(String, String, String)
	 */
	public String getStubPortName(Method method,String prefix,String suffix){
		Assert.notNull(method, "method");
		return getStubPortName(method.getName(),prefix,suffix);
	}
	/**
	 * 返回指定方法的在stub中对应的名字
	 * @param methodName 方法名
	 * @param prefix 方法名前缀
	 * @param suffix 方法名后缀
	 */
	public String getStubPortName(String methodName,String prefix,String suffix){
		Assert.notEmpty(methodName, "methodName");
		StringBuilder sb=new StringBuilder();
		if(!Judge.isEmpty(prefix))
			sb.append(prefix);		
		sb.append(methodName);
		if(!Judge.isEmpty(suffix))
			sb.append(suffix);
		return typeNameMap.get(sb.toString());
	}
	/**
	 * 将header文件加载到内存
	 * @param gsoapHeader
	 * @return 返回文件所有文本内容的字符串
	 */
	private static String load(File gsoapHeader){
		Assert.notNull(gsoapHeader, "gsoapHeader");
		Assert.isTrue(gsoapHeader.isFile(), "gsoapHeader.isFile()");
		try {
			return new String(BinaryUtils.getBytesNotEmpty(gsoapHeader));
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}
	public String getStubClassName(Class<?> clazz){
		String prefix = getNamespacePrefix(clazz);
		if(null!=prefix)
			return String.format("%s__%s",prefix,clazz.getSimpleName());						
		return null;
	}
	public String getNamespacePrefix(Class<?> clazz){
		Assert.notNull(clazz, "clazz");
		String namespace = NAMESPACEGENERATOR.schemaNamespaceFromPackageName(clazz.getPackage().getName()).toString();
		String prefix = gsoapNamespaceMap.get(namespace);
		if(null==prefix){
			namespace=NAMESPACEGENERATOR.namespaceFromPackageName(clazz.getPackage().getName()).toString();
			prefix = gsoapNamespaceMap.get(namespace);
		}
		return prefix;
	}
	
	/**
	 *  分析gsoap .h文件,创建实际的namespace prefix与namespace的映射表
	 * @param header
	 */
	protected static Map<String, String> createGSoapNamespaceMap(String header){
		String regexpString = GSoapProperties.getInstance().getProperty(PROP_REGEXP_NAMESPACE).trim();
		Assert.notEmpty(regexpString, "regexpString");
		Pattern pattern = Pattern.compile(regexpString,Pattern.MULTILINE);
		Matcher matcher = pattern.matcher(header);
		Map<String,String>map=new HashMap<String,String>();
		while (matcher.find()) {
			map.put(matcher.group(2),matcher.group(1));
			logger.debug("namespace {} = {}",matcher.group(1),matcher.group(2));
	    }
		Assert.isTrue(!map.isEmpty(), "!map.isEmpty()");
		return map;
	}
	/**
	 *  分析gsoap .h文件,创建实际的原始类名与gosap stub代码中的类名映射表
	 * @param headerFile
	 */
	protected static Map<String, String> createTypeNameMap(String headerFile){
		String regexpString = prop.getProperty(PROP_REGEXP_CLASSMAP).trim();
		Assert.notEmpty(regexpString, "regexpString");
		Pattern pattern = Pattern.compile(regexpString,Pattern.MULTILINE);
		Matcher matcher = pattern.matcher(headerFile);
		Map<String,String>map=new HashMap<String,String>();
		while (matcher.find()) {
			String stubClassName=matcher.group(1);
			// 将_USCORE替换为_
			String cppClassName=matcher.group(3).replace(underscore, "_");			
			map.put(cppClassName, stubClassName);
			logger.debug("type name map {} = {}",cppClassName,stubClassName);
	    }
		Assert.isTrue(!map.isEmpty(), "!map.isEmpty()");
		return map;
	}
	/**
	 *  分析gsoap .h文件,创建引用类型名与gosap stub代码中的类名映射表
	 * @param headerFile
	 */
	protected Map<Class<?>, String> createReferenceClassMap(String headerFile){
		String regexpString = prop.getProperty(PROP_REFERENCE_CLASS).trim();		
		Assert.notEmpty(regexpString, "regexpString");
		Pattern pattern = Pattern.compile(regexpString,Pattern.MULTILINE);
		Matcher matcher = pattern.matcher(headerFile);
		Map<Class<?>, String>map=new HashMap<Class<?>,String>();
		while (matcher.find()) {
			String stubClassName=matcher.group(1);
			// 将_USCORE替换为_			
			Class<?> clazz = classFromStubClassName(stubClassName.replace(underscore, "_"));
			map.put(clazz, stubClassName);
			logger.debug("reference class map {} = {}",clazz.getName(),stubClassName);
	    }
		if(map.isEmpty())
			logger.info("no reference class defined");
		return map;
	}

	/**
	 * 创建所有引用
	 * @param classSet
	 */
	private static Map<Class<?>, Map<String, PropertyDescriptor>> createBeanClassPropertiesMap(
			Set<Class<?>> classSet) {
		Map<Class<?>, Map<String, PropertyDescriptor>> map = new HashMap<Class<?>, Map<String, PropertyDescriptor>>();
		for (Class<?> refClass : classSet) {
			map.put(refClass, BEAN_SUPPORT.getProperties(refClass, 3));
		}
		return map;
	}
	
	/**
	 * 
	 * 分析gsoap .h文件,获取所有stub类型的属性定义
	 * @param stubClassSet
	 */
	private Map<String, Map<String, String>> createStubClassPropDefMap(Set<String> stubClassSet){
		Assert.notNull(stubClassSet, "stubClassSet");
		Map<String,Map<String,String>>map=new HashMap<String,Map<String,String>>();
		String regClassDef = prop.getProperty(PROP_REGEXP_CLASSDEF);
		String regPropDef = prop.getProperty(PROP_REGEXP_PROPDEF);	
		Assert.notEmpty(regClassDef, "regClassDef");		
		Assert.notEmpty(regPropDef, "regPropDef");
		Pattern pattern=Pattern.compile(regPropDef, Pattern.MULTILINE);
		for(String type:stubClassSet){
			logger.debug("class {}{",type);
			Matcher mc = Pattern.compile(regClassDef.replace("$CLASS_NAME$",type), Pattern.MULTILINE).matcher(stubHeader);
			if(!mc.find())
				throw new IllegalArgumentException(String.format("regexp not match Class Definition of  %s in header file",type));
			String props=mc.group(1);
			Matcher matcher = pattern.matcher(props);
			Map<String,String> propMap=new HashMap<String,String>();
			while(matcher.find()){
				propMap.put(matcher.group(2).trim(), matcher.group(1).trim());
				logger.debug("\t{} : {}",matcher.group(1),matcher.group(2));
			}
			logger.debug("}",type);
			map.put(type, propMap);
		}
		return map;
	}

	/**
	 * 将gsoap stub中的类名转换为java Class
	 * @param stubClassName gsoap stub中的类名
	 */
	public Class<?> classFromStubClassName(String stubClassName){		
		Pattern pattern = Pattern.compile("^\\b([a-zA-Z\\d]+\\d+)__([a-zA-Z\\d]+)?$");
		// 正则匹配前先将_USCORE替换为_
		Matcher matcher = pattern.matcher(stubClassName.replace(underscore, "_"));
		if(matcher.find()){
			String namespace = CodeGenUtils.getKey(this.gsoapNamespaceMap,matcher.group(1));
			return CodeGenUtils.classFromNamespaceAndClassName(namespace,matcher.group(2));
		}else
			throw new IllegalArgumentException("invalid stubClassName");
	}

	/**
	 * 将指定的变量名下划线用 {@link #underscore}替换返回stub类中的变量名
	 * @param name
	 */
	public String toStubName(String name){
		Assert.notNull(name, "name");
		return name.replace("_", underscore);
	}
	/**
	 * @return typeNameMap
	 */
	public Map<String, String> getTypeNameMap() {
		return typeNameMap;
	}

	/**
	 * @return refClassPropertiesMap
	 */
	public Map<Class<?>, Map<String, PropertyDescriptor>> getRefClassPropertiesMap() {
		return refClassPropertiesMap;
	}

	boolean isPointer(String stubClassName){
		Assert.notEmpty(stubClassName, "stubClassName");
		return stubClassName.endsWith("*");
	}
	/**
	 * 返回指定属性的类型
	 * @param prop
	 */
	public static Class<?> getClassOfProperty(PropertyDescriptor prop){
		Assert.notNull(prop,"prop");
		Method method = prop.getReadMethod();
		if(null==method)
			throw new IllegalArgumentException(String.format("not found read method for %s",prop.getName()));
		return method.getReturnType();
	}
	/**
	 * 获取stubClass中指定属性prop的类型描述
	 * @param stubClassName
	 * @param prop
	 */
	public Entry<String, String> getStubPropertyDefine(String stubClassName,String prop){
		Assert.notEmpty(stubClassName, "className");
		Assert.notEmpty(prop, "prop");
		Map<String, String> properties = stubClassProppertiesMap.get(stubClassName);
		//String stubPropName=stubClassName.replace("_", underscore);	
		for( Entry<String, String> entry:properties.entrySet()){
			if(entry.getKey().matches("^"+prop+"_?$"))
				return entry;
		}
		throw new IllegalArgumentException(String.format("not found property %s for %s",prop,stubClassName));
	}
	/**
	 * 返回stub类型中的属性名在引用类型(refClass)对应的{@link PropertyDescriptor}对象
	 * @param refClass 引用类型
	 * @param stubProp stub类型中的属性名
	 */
	public PropertyDescriptor getPropertyDescriptor(Class<?> refClass,String stubProp){
		Assert.notNull(refClass, "refClass");
		Assert.notEmpty(stubProp, "stubProp");
		Map<String, PropertyDescriptor> properties = refClassPropertiesMap.get(refClass);
		if(null==properties)
			throw new IllegalArgumentException(String.format("%s not a reference class(不是引用类型)",refClass.getName()));
		String stubPropName=stubProp.replace(underscore, "_");	
		for( Entry<String, PropertyDescriptor> entry:properties.entrySet()){
			if(stubPropName.matches("^"+entry.getKey()+"_?$"))
				return entry.getValue();
		}
		throw new IllegalArgumentException(String.format("not found property %s for %s",stubProp,refClass.getName()));
	}
	/**
	 * java类名转为cpp的类名(包括namespace)
	 * @param className
	 */
	public static String toCppFullName(String className){
		Assert.notEmpty(className, "className");
		return className.replaceAll("\\.", "::");
	}
	/**
	 * @param clazz
	 * @see #toCppFullName(String)
	 */
	public static String toCppFullName(Class<?> clazz){
		Assert.notNull(clazz, "className");
		return toCppFullName(clazz.getName());
	}
	/**
	 * java基本数据类型与cpp类型的映射表
	 */
	private static final Map<Class<?>, String> BASE_TYPE_MAP = new HashMap<Class<?>, String>() {
		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;
		{
			put(void.class,"void");
			put(int.class,"int");
			put(long.class,"long");
			put(byte.class,"char");
			put(double.class,"double");
			put(float.class,"");
			put(char.class,"char");
			put(short.class,"short");
			put(boolean.class,"bool");
			put(Integer.class,"std::shared_ptr<int>");
			put(Long.class,"std::shared_ptr<long>");
			put(Byte.class,"std::shared_ptr<char>");
			put(Double.class,"std::shared_ptr<double>");
			put(Float.class,"std::shared_ptr<float>");
			put(Character.class,"std::shared_ptr<<char>");
			put(Short.class,"std::shared_ptr<short>");
			put(Boolean.class,"std::shared_ptr<bool>");
			put(String.class,"std::string");
			put(byte[].class,"std::shared_ptr<std::vector<char>>");
		}
	};
	/**
	 * 返回java类对应的cpp类名
	 * primitive类型void类型原样输出
	 * byte[]转为{@code std::shared_ptr<std::vector<char>>}<br>
	 * String转为std::string<br>
	 * 数组类型用std::vector封装<br>
	 * 普通Object类型使用std::shared_ptr封装<br>
	 * @param clazz
	 * @param full 是否要包含namespace的cpp类名
	 */
	public static String toCppType(Class<?> clazz,boolean full){
		Assert.notNull(clazz, "clazz");
		if(BASE_TYPE_MAP.containsKey(clazz))return BASE_TYPE_MAP.get(clazz);
		else if(clazz.isArray())return String.format("std::vector<%s>", toCppType(clazz.getComponentType(),full));// 递归
		else return String.format("std::shared_ptr<%s>", full?toCppFullName(clazz):clazz.getSimpleName());		
	}
	/**
	 * @param clazz
	 * @see #toCppType(Class, boolean)
	 */
	public static String toCppType(Class<?> clazz){
		return toCppType(clazz,false);
	}
	/**
	 * 返回java类型对应的cpp参数类型
	 * @param clazz
	 * @param full
	 * @see #toCppType(Class, boolean)
	 */
	public static String toCppParameterType(Class<?> clazz,boolean full){
		String typeName = toCppType(clazz,full);
		if(typeName.startsWith("std::")){
			typeName="const "+typeName+"&";
		}
		return typeName;
	}
	/**
	 * @param clazz
	 * @see #toCppParameterType(Class, boolean)
	 */
	public static String toCppParameterType(Class<?> clazz){
		return toCppParameterType(clazz,false);
	}
	/**
	 * 返回基础缩进量
	 * @param packageName
	 */
	public static String getBaseIndentTabs(String packageName){
		if(Judge.isEmpty(packageName))return "";
		String[] names = packageName.split("\\.");
		byte[] ba = new byte[names.length];
		for(int i=0;i<ba.length;++i)ba[i]='\t';
		return new String(ba,0,ba.length-1);
	}
	public static String getBaseIndentTabs(Class<?> clazz){
		Assert.notNull(clazz, "clazz");
		return getBaseIndentTabs(clazz.getPackage().getName());
	}
	private static ThreadLocal<String[]> _packageName=new ThreadLocal<String[]>() ;
	public static String namespaceBegin(String packageName){		
		if(Judge.isEmpty(packageName))return "";
		String[] names = packageName.split("\\.");
		_packageName.set(names);
		StringBuilder builder=new StringBuilder();
		byte[] ba = new byte[names.length];
		for(int i=0;i<ba.length;++i)ba[i]='\t';
		for(int i=0;i<names.length;++i){
			String tabs = new String(ba,0,i);
			builder.append(String.format("%snamespace %s\n%s{\n",tabs,names[i],tabs));
		}
		return builder.toString();		
	}
	public static String namespaceBegin(Class<?> clazz){
		Assert.notNull(clazz, "clazz");
		return namespaceBegin(clazz.getPackage().getName());
	}
	
	public static String namespaceEnd(){		
		String[] names = _packageName.get();		
		if(Judge.isEmpty(names))return "";
		StringBuilder builder=new StringBuilder();
		byte[] ba = new byte[names.length];
		for(int i=0;i<ba.length;++i)ba[i]='\t';
		for(int i=names.length;i>0;--i){
			String tabs = new String(ba,0,i-1);
			builder.append(String.format("%s} /* namespace %s */\n",tabs,names[i-1]));
		}
		_packageName.set(null);
		return builder.toString();		
	}	
	
	/**
	 * @return stubClassProppertiesMap
	 */
	public Map<String, Map<String, String>> getStubClassProppertiesMap() {
		return stubClassProppertiesMap;
	}
	public boolean isReferenceClass(Class<?>type){
		Assert.notNull(type, "type");
		return this.referenceClassMap.containsKey(type);
	}
	/**
	 * @return stubPrefix
	 */
	public String getStubPrefix() {
		return stubPrefix;
	}
	/**
	 * @return stubFolder
	 */
	public File getStubFolder() {
		return stubFolder;
	}
	public final String toStubExpectionClassName(Class<? extends Throwable> expection) {
		Assert.notNull(expection, "expection");
		return String.format("%s__%s%s", getNamespacePrefix(this.serviceClass),this.serviceClass.getSimpleName(),expection.getSimpleName());
	}
}
